package com.sj.purchase.modules.cost.service.stock;

import com.sj.purchase.common.utils.mapper.BeanMapper;
import com.sj.purchase.manager.SkuInfo;
import com.sj.purchase.manager.SkuInfoManager;
import com.sj.purchase.modules.cost.domain.WarehouseEnum;
import com.sj.purchase.modules.cost.domain.order.InOrder;
import com.sj.purchase.modules.cost.domain.order.InOrderItem;
import com.sj.purchase.modules.cost.domain.price.CostPrice;
import com.sj.purchase.modules.cost.repository.order.InOrderItemRepository;
import com.sj.purchase.modules.cost.repository.order.OutOrderItemRepository;
import com.sj.purchase.modules.cost.repository.stock.SkuStockRepository;
import com.sj.purchase.modules.cost.service.price.CostPriceService;
import com.sj.purchase.modules.cost.utils.DoubleUtil;
import com.sj.purchase.modules.cost.views.stock.*;
import com.sj.purchase.util.DateUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.function.BiConsumer;
import java.util.stream.Collectors;

/**
 * 库存报表
 * Created by yangrd on 2017/7/27.
 */
@Service
public class StockReportService {

    @Autowired
    private InOrderItemRepository inOrderItemRepository;

    @Autowired
    private OutOrderItemRepository outOrderItemRepository;

    @Autowired
    private SkuStockRepository skuStockRepository;


    @Autowired
    private CostPriceService costPriceService;
    @Autowired
    private SkuInfoManager skuInfoManager;

    private static Pair<List<OutStockVO>, Long> emptyOutStockPair = Pair.of(new ArrayList<>(0), 0L);

    private static Pair<List<BeginStockVO>, Long> emptyBeginStockPair = Pair.of(new ArrayList<>(0), 0L);

    private static Pair<List<EndStockVO>, Long> emptyEndStockPair = Pair.of(new ArrayList<>(0), 0L);

    /**
     * 入库信息和总条目数
     */
    public Pair<List<InStockVO>, Long> getInStockContentAndTotal(Date day, String sku, Pageable pageable) {
        Map<String, List<CostPrice>> skuCostPriceCache = costPriceService.getSkuCostPriceCache(costPriceService.listCostPrice(DateUtils.addDays(day, -10), day));
        Date end = DateUtils.addSeconds(DateUtils.addDays(day, 1), -1);//今天 23:59:59
        Page<InOrderItem> inOrderItemPage = inOrderItemRepository.findByOrderCreateDateBetweenAndSkuNoContaining(day, end, sku, pageable);

        List<InStockVO> inOrderItemList = new ArrayList<>();
        for (InOrderItem inOrderItem:inOrderItemPage){
            Double skuAvgPrice = costPriceService.getCostPrice(skuCostPriceCache, inOrderItem.getSkuNo(), day);
            inOrderItemList.add(buildInStockVO(inOrderItem,skuAvgPrice));
        }
        return Pair.of(inOrderItemList, inOrderItemPage.getTotalElements());
    }

    /**
     * 出库信息和总条目数
     */
    public Pair<List<OutStockVO>, Long> getOutStockContentAndTotal(Date day, String sku, Pageable pageable) {
        Page<String> skuNoPage = outOrderItemRepository.listSkuNoByDayAndSkuLike(day, sku, pageable);
        List<String> skuNoList = skuNoPage.getContent();
        //根据sku构建出库对象
        Date end = DateUtils.getEndOfDay(day);
        Map<String, OutStockVO> outStockVOMap = skuNoList.stream().map(this::buildOutStockVO).peek(outStockVO -> outStockVO.setDate(end)).collect(Collectors.toMap(OutStockVO::getSkuNo, o -> o));
        if (skuNoList.isEmpty()) {
            return emptyOutStockPair;
        }
        List<Object[]> outWarehouseSkuNum = outOrderItemRepository.listSkuNumGroupByWarehouse(day, skuNoList);
        outWarehouseSkuNum.forEach(array -> {
            String skuNo = array[0].toString();
            WarehouseEnum warehouseEnum = WarehouseEnum.valueOf(array[1].toString());
            Long num = (Long) array[2];
            OutStockVO outStockVO = outStockVOMap.get(skuNo);
            setWarehouseStockNum(outStockVO, warehouseEnum, num);
        });
        Map<String, List<CostPrice>> skuCostPriceCache = costPriceService.getSkuCostPriceCache(costPriceService.listCostPrice(DateUtils.addDays(day, -10), DateUtils.addMilliseconds(DateUtils.addDays(day, 1),1)));
        List<OutStockVO> content = outStockVOMap.entrySet().stream().map(stringOutStockVOEntry -> stringOutStockVOEntry.getValue()).peek(outStockVO -> outStockVO.setAvgPrice(costPriceService.getCostPrice(skuCostPriceCache, outStockVO.getSkuNo(), outStockVO.getDate()))).collect(Collectors.toList());
        return Pair.of(content, skuNoPage.getTotalElements());
    }

    /**
     * 期初库存信息和总条目数
     */
    public Pair<List<BeginStockVO>, Long> getBeginStockContentAndTotal(Date day, String sku, Pageable pageable) {
        Page<String> skuNoPage = skuStockRepository.listSkuNoByDayAndSkuLike(day, sku, pageable);
        return getBaseStockContentAndTotal0(skuNoPage, day);
    }

    private Pair<List<BeginStockVO>, Long> getBaseStockContentAndTotal0(Page<String> skuNoPage, Date day) {
        List<String> skuNoList = skuNoPage.getContent();
        if (skuNoList.isEmpty()) {
            return emptyBeginStockPair;
        }
        Map<String, List<CostPrice>> skuCostPriceCache = costPriceService.getSkuCostPriceCache(costPriceService.listCostPrice(DateUtils.addDays(day, -10), DateUtils.addMilliseconds(DateUtils.addDays(day, 1),1)));
        List<BeginStockVO> content = skuNoList.stream().map(skuNo -> {
            Double skuAvgPrice = costPriceService.getCostPrice(skuCostPriceCache, skuNo, day);
            return basicStockSkuInfo(BeginStockVO.instance(), skuNo).setDate(day).setAvePrice(skuAvgPrice);
        }).collect(Collectors.toList());
        Map<String, BeginStockVO> skuBeginStockMap = content.stream().collect(Collectors.toMap(BeginStockVO::getSkuNo, o -> o));
        //按sku分组 条件 day in (skuNO) group by sku warehouse
        List<Object[]> skuAndWareAndNumList = skuStockRepository.findSkuStockGroupBySkuAndWarehouse(day, skuNoList);
        skuAndWareAndNumList.stream().forEach(array -> {
            String skuNo = (String) array[0];
            BeginStockVO stock = skuBeginStockMap.get(skuNo);
            WarehouseEnum warehouseEnum = (WarehouseEnum) array[1];
            Long num = (Long) array[2];
            //设置sku对应库存
            setWarehouseStockNum(stock, warehouseEnum, num);
        });
        return Pair.of(content, skuNoPage.getTotalElements());
    }

    /**
     * 期末库存信息和总条目数
     */
    public Pair<List<EndStockVO>, Long> getEndStockContentAndTotal(Date day, String sku, Pageable pageable) {
        Page<String> skuNoPage = skuStockRepository.findBaseStockSku(sku + "%", day, pageable);
        List<BeginStockVO> beginStockVOList = getBaseStockContentAndTotal0(skuNoPage, DateUtils.addMilliseconds(DateUtils.addDays(day,1),-1)).getLeft();
        if (beginStockVOList.isEmpty()) {
            return emptyEndStockPair;
        }
        Map<String, BeginStockVO> beginStockVOMap = beginStockVOList.stream().collect(Collectors.toMap(BeginStockVO::getSkuNo, o -> o));
        List<String> baseSkuNoList = skuNoPage.getContent();
        baseSkuNoList = baseSkuNoList.isEmpty() ? null : baseSkuNoList;
        List<Object[]> inWarehouseSkuNum = inOrderItemRepository.listSkuNumGroupByWarehouse(day, baseSkuNoList);
        List<Object[]> outWarehouseSkuNum = outOrderItemRepository.listSkuNumGroupByWarehouse(day, baseSkuNoList);
        BiConsumer<Integer, Object[]> changeWarehouseStock = (addOrSub, array) -> {
            String skuNo = array[0].toString();
            WarehouseEnum warehouseEnum = WarehouseEnum.valueOf(array[1].toString());
            Long num = (Long) array[2];
            if (!beginStockVOMap.containsKey(skuNo)) {//在使用时补全
                /*期初中不包含该sku*/
                Double skuAvgPrice = costPriceService.getCostPrice(skuNo, DateUtils.addMilliseconds(DateUtils.addDays(day,1),-1));
                BeginStockVO o = basicStockSkuInfo(BeginStockVO.instance(), skuNo).setDate(day).setAvePrice(Objects.nonNull(skuAvgPrice) ? skuAvgPrice : 0D);
                beginStockVOMap.put(skuNo, o);
            }
            BeginStockVO beginStockVO = beginStockVOMap.get(skuNo);
            setWarehouseStockNum(beginStockVO, warehouseEnum, addOrSub * num);
        };
        //期初+入库
        inWarehouseSkuNum.forEach(inWarehouse -> changeWarehouseStock.accept(1, inWarehouse));
        //期初+入库-出库
        outWarehouseSkuNum.forEach(outWarehouse -> changeWarehouseStock.accept(-1, outWarehouse));
        //TODO 此处可以不用转换 BeginStockVO -> EndStockVO
        return Pair.of(BeanMapper.mapList(beginStockVOMap.values(), BeginStockVO.class, EndStockVO.class), skuNoPage.getTotalElements());
    }

    /**
     * 期初库存信息
     */
    public Page<BeginStockVO> beginStockPage(Date day, String sku, Pageable pageable) {
        Pair<List<BeginStockVO>, Long> pair = getBeginStockContentAndTotal(day, sku, pageable);
        return new PageImpl<>(pair.getLeft(), pageable, pair.getRight());
    }


    private InStockVO buildInStockVO(InOrderItem inOrderItem,Double skuAvgPrice) {
        InOrder order = inOrderItem.getOrder();
        InStockVO inStockVO = InStockVO.instance()
                .setDate(inOrderItem.getOrder().getSaveDateDay())
                .setOrderNo(order.getOrderNo())
                .setPrice(inOrderItem.getPrice());
        basicStockSkuInfo(inStockVO, inOrderItem.getSkuNo()).setInStockNum(inOrderItem.getNum()).setAvePrice(DoubleUtil.format(skuAvgPrice));
        setWarehouseStockNum(inStockVO, order.getWarehouseEnum(), inOrderItem.getNum());
        return inStockVO;
    }

    private OutStockVO buildOutStockVO(String skuNo) {
        return basicStockSkuInfo(OutStockVO.instance(), skuNo);
    }


    //构建基础的库存信息
    private <T extends IStockVO> T basicStockSkuInfo(T stock, String skuNo) {
        SkuInfo skuInfo = skuInfoManager.getSkuInfo(skuNo);
        stock.setDarwin(skuInfo.getDarwin()).setSpuNo(skuInfo.getSpucode()).setSpuName(skuInfo.getSpuname()).setSkuNo(skuNo).setSkuName(skuInfo.getSkuname());
        return stock;
    }

    //设置仓库 库存数
    private void setWarehouseStockNum(IStockVO stock, WarehouseEnum warehouseEnum, Long num) {
        Long sum;
        switch (warehouseEnum) {
            case JINANZC:
                sum = num + stock.getJinanWarehouse();
                stock.setJinanWarehouse(sum);
                break;
            case SHOUHOU:
                sum = num + stock.getShouhouWarehouse();
                stock.setShouhouWarehouse(sum);
                break;
            case SHANGHAI:
                //暂时没有
                break;
            case GUANGZHOUZC:
                sum = num + stock.getGuangdongWarehouse();
                stock.setGuangdongWarehouse(sum);
                break;
        }
    }


}
